{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "635d8ebb",
   "metadata": {},
   "source": [
    "# Functional API\n",
    "\n",
    "- Author: [Yejin Park](https://github.com/ppakyeah)\n",
    "- Peer Review: \n",
    "- Proofread : [fastjw](https://github.com/fastjw)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/03-Use-Cases/15-LangGraph-Functional-API.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/03-Use-Cases/15-LangGraph-Functional-API.ipynb)\n",
    "## Overview\n",
    "\n",
    "This tutorial covers LangGraph's Functional API, focusing on workflow automation with ```@entrypoint``` and ```@task``` decorators.\n",
    "\n",
    "Key features include state management, parallel processing, and human-in-the-loop capabilities.\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environment Setup](#environment-setup)\n",
    "- [Functional API](#functional-api)\n",
    "- [Use Cases](#use-cases)\n",
    "\n",
    "### References\n",
    "\n",
    "- [LangGraph: Functional API Document](https://langchain-ai.github.io/langgraph/concepts/functional_api/)\n",
    "- [LangGraph: Functional API Tutorial](https://github.com/langchain-ai/langgraph/blob/f239b39060096ab2c8bff0d6303781efee174a5c/docs/docs/tutorials/functional_api/functional_api_test.ipynb)\n",
    "----"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6c7aba4",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Setting up your environment is the first step. See the [Environment Setup](https://wikidocs.net/257836) guide for more details.\n",
    "\n",
    "\n",
    "**[Note]**\n",
    "\n",
    "The langchain-opentutorial is a package of easy-to-use environment setup guidance, useful functions and utilities for tutorials.\n",
    "Check out the  [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "21943adb",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f25ec196",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [\n",
    "        \"langchain_core\",\n",
    "        \"langgraph\",\n",
    "        \"langchain-openai\",\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "690a9ae0",
   "metadata": {},
   "source": [
    "You can set API keys in a ```.env``` file or set them manually.\n",
    "\n",
    "[Note] If you’re not using the ```.env``` file, no worries! Just enter the keys directly in the cell below, and you’re good to go."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "327c2c7c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "# Attempt to load environment variables from a .env file; if unsuccessful, set them manually.\n",
    "if not load_dotenv():\n",
    "    set_env(\n",
    "        {\n",
    "            \"OPENAI_API_KEY\": \"\",\n",
    "            \"LANGCHAIN_API_KEY\": \"\",\n",
    "            \"LANGCHAIN_TRACING_V2\": \"true\",\n",
    "            \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "            \"LANGCHAIN_PROJECT\": \"15-LangGraph-Functional-API\",\n",
    "        }\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa00c3f4",
   "metadata": {},
   "source": [
    "## Functional API\n",
    "The Functional API is a programming interface provided by LangGraph that extends existing Python functions with advanced features such as state management, parallel processing, and memory management, all while requiring minimal code modifications.\n",
    "\n",
    "### Core Components\n",
    "The Functional API uses two primitives to define workflows:\n",
    "1. ```@entrypoint``` Decorator\n",
    "- Defines the entry point of a workflow\n",
    "- Automates state management and checkpointing\n",
    "- Manages streaming and interruption points"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "eba33aea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "15"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from uuid import uuid4\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.func import entrypoint\n",
    "\n",
    "\n",
    "@entrypoint(checkpointer=MemorySaver())\n",
    "def calculate_sum(numbers: list[int]) -> int:\n",
    "    \"\"\"A simple workflow that sums numbers\"\"\"\n",
    "    return sum(numbers)\n",
    "\n",
    "config = {\n",
    "    \"configurable\": {\n",
    "        \"thread_id\": str(uuid4())\n",
    "    }\n",
    "}\n",
    "\n",
    "calculate_sum.invoke([1, 2, 3, 4, 5], config)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ae17ff1",
   "metadata": {},
   "source": [
    "2. ```@task``` Decorator\n",
    "- Defines units of work that can be executed asynchronously\n",
    "- Handles retry policies and error handling\n",
    "- Supports parallel processing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "0f87caa5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from uuid import uuid4\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.func import task\n",
    "\n",
    "@task()\n",
    "def multiply_number(num: int) -> int:\n",
    "    \"\"\"Simple task that multiplies a number by 2\"\"\"\n",
    "    return num * 2\n",
    "\n",
    "@entrypoint(checkpointer=MemorySaver())\n",
    "def calculate_multiply(num: int) -> int:\n",
    "    \"\"\"A simple workflow that multiplies two numbers\"\"\"\n",
    "    future = multiply_number(num)\n",
    "    return future.result()\n",
    "\n",
    "config = {\n",
    "    \"configurable\": {\n",
    "        \"thread_id\": str(uuid4())\n",
    "    }\n",
    "}\n",
    "calculate_multiply.invoke(3, config)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "995eb490",
   "metadata": {},
   "source": [
    "## Use Cases"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc6b0829",
   "metadata": {},
   "source": [
    "### Asynchronous and Parallel Processing\n",
    "\n",
    "Long-running tasks can significantly impact application performance.\n",
    "\n",
    "The Functional API allows you to execute tasks asynchronously and in parallel, improving efficiency especially for I/O-bound operations like LLMs API calls.\n",
    "\n",
    "The ```@task``` decorator makes it easy to convert regular functions into asynchronous tasks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "6dbd43e1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[2, 4, 6, 8, 10]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langgraph.func import task\n",
    "import time\n",
    "\n",
    "@task()\n",
    "def process_number(n: int) -> int:\n",
    "    \"\"\"Simulates processing by waiting for 1 second\"\"\"\n",
    "    time.sleep(1)\n",
    "    return n * 2\n",
    "\n",
    "@entrypoint()\n",
    "def parallel_processing(numbers: list[int]) -> list[int]:\n",
    "    \"\"\"Processes multiple numbers in parallel\"\"\"\n",
    "    # Start all tasks\n",
    "    futures = [process_number(n) for n in numbers]\n",
    "    return [f.result() for f in futures]\n",
    "\n",
    "parallel_processing.invoke([1, 2, 3, 4, 5])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3179e0f1",
   "metadata": {},
   "source": [
    "### Interrupts and Human Intervention\n",
    "\n",
    "Some workflows require human oversight or intervention at critical points.\n",
    "\n",
    "The Functional API provides built-in support for human-in-the-loop processes through its interrupt mechanism.\n",
    "\n",
    "This allows you to pause execution, get human input, and continue processing based on that input."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "7a3eb9e6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'step_1': 'foo bar'}\n",
      "\n",
      "\n",
      "{'__interrupt__': (Interrupt(value='Please provide feedback: foo bar', resumable=True, ns=['graph:f550c5f8-67e0-6c57-9206-c10c7affc896'], when='during'),)}\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from uuid import uuid4\n",
    "from langgraph.func import entrypoint, task\n",
    "from langgraph.types import Command, interrupt\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "\n",
    "\n",
    "@task()\n",
    "def step_1(input_query):\n",
    "    \"\"\"Append bar.\"\"\"\n",
    "    return f\"{input_query} bar\"\n",
    "\n",
    "\n",
    "@task()\n",
    "def human_feedback(input_query):\n",
    "    \"\"\"Append user input.\"\"\"\n",
    "    feedback = interrupt(f\"Please provide feedback: {input_query}\")\n",
    "    return f\"{input_query} {feedback}\"\n",
    "\n",
    "\n",
    "@task()\n",
    "def step_3(input_query):\n",
    "    \"\"\"Append qux.\"\"\"\n",
    "    return f\"{input_query} qux\"\n",
    "\n",
    "checkpointer = MemorySaver()\n",
    "\n",
    "@entrypoint(checkpointer=checkpointer)\n",
    "def graph(input_query):\n",
    "    result_1 = step_1(input_query).result()\n",
    "    feedback = interrupt(f\"Please provide feedback: {result_1}\")\n",
    "\n",
    "    result_2 = f\"{input_query} {feedback}\"\n",
    "    result_3 = step_3(result_2).result()\n",
    "\n",
    "    return result_3\n",
    "\n",
    "config = {\"configurable\": {\"thread_id\": str(uuid4())}}\n",
    "for event in graph.stream(\"foo\", config):\n",
    "    print(event)\n",
    "    print(\"\\n\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "c2cc892c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'step_3': 'foo baz qux'}\n",
      "\n",
      "\n",
      "{'graph': 'foo baz qux'}\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Continue execution\n",
    "for event in graph.stream(Command(resume=\"baz\"), config):\n",
    "    print(event)\n",
    "    print(\"\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e206724a",
   "metadata": {},
   "source": [
    "### Automated State Management\n",
    "\n",
    "The Functional API automatically handles state persistence and restoration between function calls.\n",
    "\n",
    "This is particularly useful in conversational applications where maintaining context is crucial.\n",
    "\n",
    "You can focus on your business logic while LangGraph handles the complexities of state management."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "6ef2763e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from uuid import uuid4\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_core.messages import SystemMessage, HumanMessage, BaseMessage\n",
    "from langgraph.func import entrypoint\n",
    "from langgraph.graph import add_messages\n",
    "\n",
    "\n",
    "llm = ChatOpenAI(\n",
    "    model=\"gpt-4o-mini\",\n",
    "    temperature=0\n",
    ")\n",
    "\n",
    "checkpointer = MemorySaver()\n",
    "\n",
    "# Set a checkpointer to enable persistence.\n",
    "@entrypoint(checkpointer=checkpointer)\n",
    "def conversational_agent(messages: list[BaseMessage], *, previous: list[BaseMessage] = None):\n",
    "    # Add previous messages from short-term memory to the current messages\n",
    "    if previous is not None:\n",
    "        messages = add_messages(previous, messages)\n",
    "\n",
    "    # Get agent's response based on conversation history.\n",
    "    llm_response = llm.invoke(\n",
    "         [\n",
    "            SystemMessage(\n",
    "                content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\"\n",
    "            )\n",
    "        ]\n",
    "        + messages\n",
    "    )\n",
    "\n",
    "    # Add agent's messages to conversation history\n",
    "    messages = add_messages(messages, llm_response)\n",
    "\n",
    "    return messages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "25d6c41b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Hi. I'm currently creating a tutorial, named LangChain OpenTutorial.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "That sounds like a great project! How can I assist you with your LangChain OpenTutorial? Are you looking for help with content, examples, or something else?\n"
     ]
    }
   ],
   "source": [
    "# Config\n",
    "config = {\n",
    "    \"configurable\": {\n",
    "        \"thread_id\": str(uuid4())\n",
    "    }\n",
    "}\n",
    "\n",
    "# Run with checkpointer to persist state in memory\n",
    "messages = conversational_agent.invoke([HumanMessage(content=\"Hi. I'm currently creating a tutorial, named LangChain OpenTutorial.\")], config)\n",
    "for m in messages:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "633a6e8e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Hi. I'm currently creating a tutorial, named LangChain OpenTutorial.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "That sounds like a great project! How can I assist you with your LangChain OpenTutorial? Are you looking for help with content, examples, or something else?\n"
     ]
    }
   ],
   "source": [
    "# Checkpoint state\n",
    "agent_state = conversational_agent.get_state(config)\n",
    "for m in agent_state.values:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "18bc7a55",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Hi. I'm currently creating a tutorial, named LangChain OpenTutorial.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "That sounds like a great project! How can I assist you with your LangChain OpenTutorial? Are you looking for help with content, examples, or something else?\n",
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Do you remember the name of my tutorial that I'm now working on?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Yes, you mentioned that you are creating a tutorial named \"LangChain OpenTutorial.\" How can I assist you further with it?\n"
     ]
    }
   ],
   "source": [
    "# Continue with the same thread\n",
    "messages = conversational_agent.invoke([HumanMessage(content=\"Do you remember the name of my tutorial that I'm now working on?\")], config)\n",
    "for m in messages:\n",
    "    m.pretty_print()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-opentutorial",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
